import type {
  DependencyList,
  EffectCallback,
  useEffect,
  useLayoutEffect,
} from 'react'
import { useRef } from 'react'
import { useUnmount } from '@/library/hooks/use-unmount'
import {
  type BasicTarget,
  getTargetElement,
} from '../utils/dom-target'

function depsAreSame(oldDeps: DependencyList, deps: DependencyList): boolean {
  if (oldDeps === deps) return true
  for (let i = 0; i < oldDeps.length; i++) {
    if (!Object.is(oldDeps[i], deps[i])) return false
  }
  return true
}

type EffectHookType = typeof useEffect | typeof useLayoutEffect;

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const createEffectWithTarget = (useEffectType: EffectHookType) => {
  const useEffectWithTarget = (
    effect: EffectCallback,
    deps: DependencyList,
    target: BasicTarget<any> | BasicTarget<any>[],
  ) => {
    const hasInitRef = useRef(false)

    const lastElementRef = useRef<(Element | null)[]>([])
    const lastDepsRef = useRef<DependencyList>([])

    const unLoadRef = useRef<any>()

    useEffectType(() => {
      const targets = Array.isArray(target) ? target : [target]
      const els = targets.map((item) => getTargetElement(item))

      // init run
      if (!hasInitRef.current) {
        hasInitRef.current = true
        lastElementRef.current = els
        lastDepsRef.current = deps

        unLoadRef.current = effect()
        return
      }

      if (
        els.length !== lastElementRef.current.length
        || !depsAreSame(els, lastElementRef.current)
        || !depsAreSame(deps, lastDepsRef.current)
      ) {
        unLoadRef.current?.()

        lastElementRef.current = els
        lastDepsRef.current = deps
        unLoadRef.current = effect()
      }
    })

    useUnmount(() => {
      unLoadRef.current?.()
      // for react-refresh
      hasInitRef.current = false
    })
  }

  return useEffectWithTarget
}
